using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.IO;
using log4net;
using System.Reflection;

namespace Common.Shared.Net {
	/// <summary>
	/// Provides Ftp functionality.
	/// </summary>
	public class Ftp {
		protected static readonly ILog Log = LogManager.GetLogger ( MethodBase.GetCurrentMethod ( ).DeclaringType );

		private const int READ_BUFFER_SIZE = 1024 * 5;
		private const int WRITE_BUFFER_SIZE = 1024 * 5;

		/// <summary>
		/// Occurs when the file has started to download
		/// </summary>
		public event EventHandler<FtpDownloadEventArgs> Started;
		/// <summary>
		/// Occurs when there was an error downloading the file
		/// </summary>
		public event EventHandler<ExceptionEventArgs> Error;
		/// <summary>
		/// Occurs when the file was downloaded (or exited due to error)
		/// </summary>
		public event EventHandler<FtpDownloadEventArgs> Completed;

		private string _user;
		private string _password;

		/// <summary>
		/// Initializes a new instance of the <see cref="Ftp"/> class.
		/// </summary>
		/// <param name="user">The user.</param>
		/// <param name="pass">The pass.</param>
		public Ftp ( string user, string pass ) {
			this.User = user;
			this.Password = pass;
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="Ftp"/> class.
		/// </summary>
		public Ftp ( )
			: this ( "anonymous", "anonymous@ups.com" ) {

		}

		/// <summary>
		/// Gets or sets the password.
		/// </summary>
		/// <value>The password.</value>
		public string Password {
			get { return _password; }
			set { _password = value; }
		}

		/// <summary>
		/// Gets or sets the user.
		/// </summary>
		/// <value>The user.</value>
		public string User {
			get { return _user; }
			set { _user = value; }
		}


		/// <summary>
		/// Downloads the specified remote file.
		/// </summary>
		/// <param name="remote">The remote file.</param>
		/// <param name="local">The local path to save the file.</param>
		public void Download ( Uri remote, FileInfo local ) {
			OnStarted ( new FtpDownloadEventArgs ( remote, local ) );
			try {
				FtpWebRequest req = FtpWebRequest.Create ( remote ) as FtpWebRequest;
				req.EnableSsl = remote.Scheme.Equals ( "ftps", StringComparison.CurrentCultureIgnoreCase );
				req.Credentials = new NetworkCredential ( this.User, this.Password );
				req.Method = WebRequestMethods.Ftp.DownloadFile;

				FtpWebResponse resp = req.GetResponse ( ) as FtpWebResponse;
				using ( FileStream fs = new FileStream ( local.FullName, FileMode.Create, FileAccess.Write, FileShare.ReadWrite ) ) {
					using ( Stream strm = resp.GetResponseStream ( ) ) {
						byte[] buffer = new byte[ READ_BUFFER_SIZE ];
						int i = 0;
						while ( ( i = strm.Read ( buffer, 0, buffer.Length ) ) > 0 ) {
							fs.Write ( buffer, 0, i );
						}
					}
				}
			} catch ( Exception ex ) {
				OnError ( new ExceptionEventArgs ( ex ) );
				throw;
			}

			OnCompleted ( new FtpDownloadEventArgs ( remote, local ) );
		}

		public void MakeDirectory ( Uri remote, string directoryName ) {
			FtpWebRequest req = CreateRequest ( new Uri ( remote, directoryName ) );
			req.Method = WebRequestMethods.Ftp.MakeDirectory;
			try {
				using ( FtpWebResponse resp = req.GetResponse ( ) as FtpWebResponse ) { }
			} catch ( Exception ex ) { }
		}

		public void Upload ( Uri remote, FileInfo local ) {
			try {
				if ( !local.Exists ) {
					throw new FileNotFoundException ( "File not found", local.FullName );
				}

				Uri temp = new Uri ( Path.Combine ( remote.ToString ( ), local.Name ) );
				FtpWebRequest req = CreateRequest ( temp );
				req.Method = WebRequestMethods.Ftp.UploadFile;

				using ( FileStream fileStream = local.OpenRead ( ) ) {
					using ( Stream reqStream = req.GetRequestStream ( ) ) {
						byte[] buffer = new byte[ WRITE_BUFFER_SIZE ];
						int bytesRead = 0;
						while ( ( bytesRead = fileStream.Read ( buffer, 0, buffer.Length ) ) > 0 ) {
							reqStream.Write ( buffer, 0, bytesRead );
						}
					}
				}

				using ( FtpWebResponse resp = req.GetResponse ( ) as FtpWebResponse ) { }

			} catch ( Exception ex ) {
				Log.Error ( ex.Message, ex );
			}
		}

		private FtpWebRequest CreateRequest ( Uri uri ) {
			FtpWebRequest req = FtpWebRequest.Create ( uri ) as FtpWebRequest;
			req.EnableSsl = uri.Scheme.Equals ( "ftps", StringComparison.CurrentCultureIgnoreCase );
			req.Credentials = new NetworkCredential ( this.User, this.Password );
			req.KeepAlive = false;
			return req;
		}

		/// <summary>
		/// Downloads the specified remote file asynchronously.
		/// </summary>
		/// <param name="remote">The remote file.</param>
		/// <param name="local">The local path to save the file.</param>
		public void DownloadAsync ( Uri remote, FileInfo local ) {
			throw new NotImplementedException ( "Not yet implemented." );
		}

		/// <summary>
		/// Raises the <see cref="E:Started"/> event.
		/// </summary>
		/// <param name="e">The <see cref="MisloadScanner.Core.Net.FtpDownloadEventArgs"/> instance containing the event data.</param>
		protected void OnStarted ( FtpDownloadEventArgs e ) {
			if ( this.Started != null ) {
				this.Started ( this, e );
			}
		}

		/// <summary>
		/// Raises the <see cref="E:Error"/> event.
		/// </summary>
		/// <param name="e">The <see cref="MisloadScanner.Core.Components.ExceptionEventArgs"/> instance containing the event data.</param>
		protected void OnError ( ExceptionEventArgs e ) {
			if ( this.Error != null ) {
				this.Error ( this, e );
			}
		}

		/// <summary>
		/// Raises the <see cref="E:Completed"/> event.
		/// </summary>
		/// <param name="e">The <see cref="MisloadScanner.Core.Net.FtpDownloadEventArgs"/> instance containing the event data.</param>
		protected void OnCompleted ( FtpDownloadEventArgs e ) {
			if ( this.Completed != null ) {
				this.Completed ( this, e );
			}
		}
	}
}
